﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace GLDotNet.WinForms
{
    internal sealed class WGLContext : DisposableObject, INativeContext
    {
        GLContextParams contextParams;
        IntPtr hWnd;
        IntPtr hDC;
        IntPtr hRC;
                
        public int VersionMajor
        {
            get { return this.contextParams.VersionMajor; }
        }

        public int VersionMinor
        {
            get { return this.contextParams.VersionMinor; }
        }

        public bool ForwardCompatible
        {
            get { return this.contextParams.ForwardCompatible; }
        }

        public WGLContext(GLContextParams contextParams, IntPtr hWnd)
        {
            this.contextParams = contextParams;
            this.hWnd = hWnd;
            this.hDC = Win32.GetDC(hWnd);

            if (this.hDC == IntPtr.Zero)
                throw new GLDotNetException("Could not get a device context (hDC).");

            Win32.PixelFormatDescriptor pfd = new Win32.PixelFormatDescriptor();
            pfd.nSize = (ushort)Marshal.SizeOf(typeof(Win32.PixelFormatDescriptor));
            pfd.nVersion = 1;
            pfd.dwFlags = (Win32.PFD_SUPPORT_OPENGL | Win32.PFD_DRAW_TO_WINDOW | Win32.PFD_DOUBLEBUFFER);
            pfd.iPixelType = (byte)Win32.PFD_TYPE_RGBA;
            pfd.cColorBits = (byte)Win32.GetDeviceCaps(this.hDC, Win32.BITSPIXEL);
            pfd.cDepthBits = 32;
            pfd.iLayerType = (byte)Win32.PFD_MAIN_PLANE;

            int pixelformat = Win32.ChoosePixelFormat(this.hDC, ref pfd);

            if (pixelformat == 0)
                throw new GLDotNetException("Could not find A suitable pixel format.");

            if (Win32.SetPixelFormat(this.hDC, pixelformat, ref pfd) == 0)
                throw new GLDotNetException("Could not set the pixel format.");

            IntPtr tempContext = WGL.CreateContext(this.hDC);

            if (tempContext == IntPtr.Zero)
                throw new GLDotNetException("Unable to create temporary render context.");

            if (!WGL.MakeCurrent(hDC, tempContext))
                throw new GLDotNetException("Unable to make temporary render context current.");

            int[] attribs = new int[]
			{
				(int)WGL.ContextMajorVersionArb, this.contextParams.VersionMajor,
				(int)WGL.ContextMinorVersionArb, this.contextParams.VersionMinor,
                (int)WGL.ContextFlagsArb, (int)(this.contextParams.ForwardCompatible ? WGL.ContextForwardCompatibleBitArb : 0),
				0
			};

            WGL.CreateContextAttribsARB createContextAttribs = (WGL.CreateContextAttribsARB)this.LoadExtensionFunction<WGL.CreateContextAttribsARB>("wgl");
            this.hRC = createContextAttribs(this.hDC, IntPtr.Zero, attribs);

            WGL.MakeCurrent(IntPtr.Zero, IntPtr.Zero);
            WGL.DeleteContext(tempContext);

            if (this.hRC == null)
                throw new GLDotNetException("Unable to create render context.");

            if (!WGL.MakeCurrent(this.hDC, this.hRC))
                throw new GLDotNetException("Unable to make render context current.");
        }

        protected override void DisposeManagedResources()
        {
        }

        protected override void DisposeUnmanagedResources()
        {
            WGL.MakeCurrent(IntPtr.Zero, IntPtr.Zero);
            WGL.DeleteContext(this.hRC);

            Win32.ReleaseDC(this.hWnd, this.hDC);
        }

        public bool MakeCurrent()
        {
            return WGL.MakeCurrent(this.hDC, this.hRC);
        }

        public bool SwapBuffers()
        {
            return WGL.SwapBuffers(this.hDC);
        }

        public object LoadExtensionFunction<T>(string prefix)
        {
            Type delegateType = typeof(T);

            string name = prefix + delegateType.Name;

            IntPtr proc = WGL.GetProcAddress(name);

            if (proc == IntPtr.Zero)
                throw new GLDotNetException("Failed to load extension function \"" + name + "\".");

            ErrorCode error = GLUtility.GetError();

            if (error != GL.NoError)
                throw new GLDotNetException("Loading extension function cased error: " + error + ".");

            return Marshal.GetDelegateForFunctionPointer(proc, delegateType);
        }
    }
}
